Passed
Push — master ( 445067...6ce435 )
by Johan
02:18
created

Referencer   F

Complexity

Total Complexity 87

Size/Duplication

Total Lines 501
Duplicated Lines 100 %

Importance

Changes 0
Metric Value
wmc 87
eloc 275
dl 501
loc 501
rs 2
c 0
b 0
f 0

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Referencer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*
2
  Copyright (C) 2015 Yusuke Suzuki <[email protected]>
3
4
  Redistribution and use in source and binary forms, with or without
5
  modification, are permitted provided that the following conditions are met:
6
7
    * Redistributions of source code must retain the above copyright
8
      notice, this list of conditions and the following disclaimer.
9
    * Redistributions in binary form must reproduce the above copyright
10
      notice, this list of conditions and the following disclaimer in the
11
      documentation and/or other materials provided with the distribution.
12
13
  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16
  ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
17
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22
  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
*/
24
"use strict";
25
26
/* eslint-disable no-underscore-dangle */
27
/* eslint-disable no-undefined */
28
29
const Syntax = require("estraverse").Syntax;
30
const esrecurse = require("esrecurse");
31
const Reference = require("./reference");
32
const Variable = require("./variable");
33
const PatternVisitor = require("./pattern-visitor");
34
const definition = require("./definition");
35
const assert = require("assert");
36
37
const ParameterDefinition = definition.ParameterDefinition;
38
const Definition = definition.Definition;
39
40
/**
41
 * Traverse identifier in pattern
42
 * @param {Object} options - options
43
 * @param {pattern} rootPattern - root pattern
44
 * @param {Refencer} referencer - referencer
45
 * @param {callback} callback - callback
46
 * @returns {void}
47
 */
48
function traverseIdentifierInPattern(options, rootPattern, referencer, callback) {
49
50
    // Call the callback at left hand identifier nodes, and Collect right hand nodes.
51
    const visitor = new PatternVisitor(options, rootPattern, callback);
52
53
    visitor.visit(rootPattern);
54
55
    // Process the right hand nodes recursively.
56
    if (referencer !== null && referencer !== undefined) {
57
        visitor.rightHandNodes.forEach(referencer.visit, referencer);
58
    }
59
}
60
61
// Importing ImportDeclaration.
62
// http://people.mozilla.org/~jorendorff/es6-draft.html#sec-moduledeclarationinstantiation
63
// https://github.com/estree/estree/blob/master/es6.md#importdeclaration
64
// FIXME: Now, we don't create module environment, because the context is
65
// implementation dependent.
66
67
class Importer extends esrecurse.Visitor {
68
    constructor(declaration, referencer) {
69
        super(null, referencer.options);
70
        this.declaration = declaration;
71
        this.referencer = referencer;
72
    }
73
74
    visitImport(id, specifier) {
75
        this.referencer.visitPattern(id, pattern => {
76
            this.referencer.currentScope().__define(pattern,
77
                new Definition(
78
                    Variable.ImportBinding,
79
                    pattern,
80
                    specifier,
81
                    this.declaration,
82
                    null,
83
                    null
84
                ));
85
        });
86
    }
87
88
    ImportNamespaceSpecifier(node) {
89
        const local = (node.local || node.id);
90
91
        if (local) {
92
            this.visitImport(local, node);
93
        }
94
    }
95
96
    ImportDefaultSpecifier(node) {
97
        const local = (node.local || node.id);
98
99
        this.visitImport(local, node);
100
    }
101
102
    ImportSpecifier(node) {
103
        const local = (node.local || node.id);
104
105
        if (node.name) {
106
            this.visitImport(node.name, node);
107
        } else {
108
            this.visitImport(local, node);
109
        }
110
    }
111
}
112
113
// Referencing variables and creating bindings.
114
class Referencer extends esrecurse.Visitor {
115
    constructor(options, scopeManager) {
116
        super(null, options);
117
        this.options = options;
118
        this.scopeManager = scopeManager;
119
        this.parent = null;
120
        this.isInnerMethodDefinition = false;
121
    }
122
123
    currentScope() {
124
        return this.scopeManager.__currentScope;
125
    }
126
127
    close(node) {
128
        while (this.currentScope() && node === this.currentScope().block) {
129
            this.scopeManager.__currentScope = this.currentScope().__close(this.scopeManager);
130
        }
131
    }
132
133
    pushInnerMethodDefinition(isInnerMethodDefinition) {
134
        const previous = this.isInnerMethodDefinition;
135
136
        this.isInnerMethodDefinition = isInnerMethodDefinition;
137
        return previous;
138
    }
139
140
    popInnerMethodDefinition(isInnerMethodDefinition) {
141
        this.isInnerMethodDefinition = isInnerMethodDefinition;
142
    }
143
144
    referencingDefaultValue(pattern, assignments, maybeImplicitGlobal, init) {
145
        const scope = this.currentScope();
146
147
        assignments.forEach(assignment => {
148
            scope.__referencing(
149
                pattern,
150
                Reference.WRITE,
151
                assignment.right,
152
                maybeImplicitGlobal,
153
                pattern !== assignment.left,
154
                init
155
            );
156
        });
157
    }
158
159
    visitPattern(node, options, callback) {
160
        let visitPatternOptions = options;
161
        let visitPatternCallback = callback;
162
163
        if (typeof options === "function") {
164
            visitPatternCallback = options;
165
            visitPatternOptions = { processRightHandNodes: false };
166
        }
167
168
        traverseIdentifierInPattern(
169
            this.options,
170
            node,
171
            visitPatternOptions.processRightHandNodes ? this : null,
172
            visitPatternCallback
173
        );
174
    }
175
176
    visitFunction(node) {
177
        let i, iz;
178
179
        // FunctionDeclaration name is defined in upper scope
180
        // NOTE: Not referring variableScope. It is intended.
181
        // Since
182
        //  in ES5, FunctionDeclaration should be in FunctionBody.
183
        //  in ES6, FunctionDeclaration should be block scoped.
184
185
        if (node.type === Syntax.FunctionDeclaration) {
186
187
            // id is defined in upper scope
188
            this.currentScope().__define(node.id,
189
                new Definition(
190
                    Variable.FunctionName,
191
                    node.id,
192
                    node,
193
                    null,
194
                    null,
195
                    null
196
                ));
197
        }
198
199
        // FunctionExpression with name creates its special scope;
200
        // FunctionExpressionNameScope.
201
        if (node.type === Syntax.FunctionExpression && node.id) {
202
            this.scopeManager.__nestFunctionExpressionNameScope(node);
203
        }
204
205
        // Consider this function is in the MethodDefinition.
206
        this.scopeManager.__nestFunctionScope(node, this.isInnerMethodDefinition);
207
208
        const that = this;
209
210
        /**
211
         * Visit pattern callback
212
         * @param {pattern} pattern - pattern
213
         * @param {Object} info - info
214
         * @returns {void}
215
         */
216
        function visitPatternCallback(pattern, info) {
217
            that.currentScope().__define(pattern,
218
                new ParameterDefinition(
219
                    pattern,
220
                    node,
221
                    i,
222
                    info.rest
223
                ));
224
225
            that.referencingDefaultValue(pattern, info.assignments, null, true);
226
        }
227
228
        // Process parameter declarations.
229
        for (i = 0, iz = node.params.length; i < iz; ++i) {
230
            this.visitPattern(node.params[i], { processRightHandNodes: true }, visitPatternCallback);
231
        }
232
233
        // if there's a rest argument, add that
234
        if (node.rest) {
235
            this.visitPattern({
236
                type: "RestElement",
237
                argument: node.rest
238
            }, pattern => {
239
                this.currentScope().__define(pattern,
240
                    new ParameterDefinition(
241
                        pattern,
242
                        node,
243
                        node.params.length,
244
                        true
245
                    ));
246
            });
247
        }
248
249
        // In TypeScript there are a number of function-like constructs which have no body,
250
        // so check it exists before traversing
251
        if (node.body) {
252
253
            // Skip BlockStatement to prevent creating BlockStatement scope.
254
            if (node.body.type === Syntax.BlockStatement) {
255
                this.visitChildren(node.body);
256
            } else {
257
                this.visit(node.body);
258
            }
259
        }
260
261
        this.close(node);
262
    }
263
264
    visitClass(node) {
265
        if (node.type === Syntax.ClassDeclaration) {
266
            this.currentScope().__define(node.id,
267
                new Definition(
268
                    Variable.ClassName,
269
                    node.id,
270
                    node,
271
                    null,
272
                    null,
273
                    null
274
                ));
275
        }
276
277
        this.visit(node.superClass);
278
279
        this.scopeManager.__nestClassScope(node);
280
281
        if (node.id) {
282
            this.currentScope().__define(node.id,
283
                new Definition(
284
                    Variable.ClassName,
285
                    node.id,
286
                    node
287
                ));
288
        }
289
        this.visit(node.body);
290
291
        this.close(node);
292
    }
293
294
    visitProperty(node) {
295
        let previous;
296
297
        if (node.computed) {
298
            this.visit(node.key);
299
        }
300
301
        const isMethodDefinition = node.type === Syntax.MethodDefinition;
302
303
        if (isMethodDefinition) {
304
            previous = this.pushInnerMethodDefinition(true);
305
        }
306
        this.visit(node.value);
307
        if (isMethodDefinition) {
308
            this.popInnerMethodDefinition(previous);
309
        }
310
    }
311
312
    visitForIn(node) {
313
        if (node.left.type === Syntax.VariableDeclaration && node.left.kind !== "var") {
314
            this.scopeManager.__nestForScope(node);
315
        }
316
317
        if (node.left.type === Syntax.VariableDeclaration) {
318
            this.visit(node.left);
319
            this.visitPattern(node.left.declarations[0].id, pattern => {
320
                this.currentScope().__referencing(pattern, Reference.WRITE, node.right, null, true, true);
321
            });
322
        } else {
323
            this.visitPattern(node.left, { processRightHandNodes: true }, (pattern, info) => {
324
                let maybeImplicitGlobal = null;
325
326
                if (!this.currentScope().isStrict) {
327
                    maybeImplicitGlobal = {
328
                        pattern,
329
                        node
330
                    };
331
                }
332
                this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false);
333
                this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, true, false);
334
            });
335
        }
336
        this.visit(node.right);
337
        this.visit(node.body);
338
339
        this.close(node);
340
    }
341
342
    visitVariableDeclaration(variableTargetScope, type, node, index) {
343
344
        const decl = node.declarations[index];
345
        const init = decl.init;
346
347
        this.visitPattern(decl.id, { processRightHandNodes: true }, (pattern, info) => {
348
            variableTargetScope.__define(
349
                pattern,
350
                new Definition(
351
                    type,
352
                    pattern,
353
                    decl,
354
                    node,
355
                    index,
356
                    node.kind
357
                )
358
            );
359
360
            this.referencingDefaultValue(pattern, info.assignments, null, true);
361
            if (init) {
362
                this.currentScope().__referencing(pattern, Reference.WRITE, init, null, !info.topLevel, true);
363
            }
364
        });
365
    }
366
367
    AssignmentExpression(node) {
368
        if (PatternVisitor.isPattern(node.left)) {
369
            if (node.operator === "=") {
370
                this.visitPattern(node.left, { processRightHandNodes: true }, (pattern, info) => {
371
                    let maybeImplicitGlobal = null;
372
373
                    if (!this.currentScope().isStrict) {
374
                        maybeImplicitGlobal = {
375
                            pattern,
376
                            node
377
                        };
378
                    }
379
                    this.referencingDefaultValue(pattern, info.assignments, maybeImplicitGlobal, false);
380
                    this.currentScope().__referencing(pattern, Reference.WRITE, node.right, maybeImplicitGlobal, !info.topLevel, false);
381
                });
382
            } else {
383
                this.currentScope().__referencing(node.left, Reference.RW, node.right);
384
            }
385
        } else {
386
            this.visit(node.left);
387
        }
388
        this.visit(node.right);
389
    }
390
391
    CatchClause(node) {
392
        this.scopeManager.__nestCatchScope(node);
393
394
        this.visitPattern(node.param, { processRightHandNodes: true }, (pattern, info) => {
395
            this.currentScope().__define(pattern,
396
                new Definition(
397
                    Variable.CatchClause,
398
                    node.param,
399
                    node,
400
                    null,
401
                    null,
402
                    null
403
                ));
404
            this.referencingDefaultValue(pattern, info.assignments, null, true);
405
        });
406
        this.visit(node.body);
407
408
        this.close(node);
409
    }
410
411
    Program(node) {
412
        this.scopeManager.__nestGlobalScope(node);
413
414
        if (this.scopeManager.__isNodejsScope()) {
415
416
            // Force strictness of GlobalScope to false when using node.js scope.
417
            this.currentScope().isStrict = false;
418
            this.scopeManager.__nestFunctionScope(node, false);
419
        }
420
421
        if (this.scopeManager.__isES6() && this.scopeManager.isModule()) {
422
            this.scopeManager.__nestModuleScope(node);
423
        }
424
425
        if (this.scopeManager.isStrictModeSupported() && this.scopeManager.isImpliedStrict()) {
426
            this.currentScope().isStrict = true;
427
        }
428
429
        this.visitChildren(node);
430
        this.close(node);
431
    }
432
433
    Identifier(node) {
434
        this.currentScope().__referencing(node);
435
    }
436
437
    UpdateExpression(node) {
438
        if (PatternVisitor.isPattern(node.argument)) {
439
            this.currentScope().__referencing(node.argument, Reference.RW, null);
440
        } else {
441
            this.visitChildren(node);
442
        }
443
    }
444
445
    MemberExpression(node) {
446
        this.visit(node.object);
447
        if (node.computed) {
448
            this.visit(node.property);
449
        }
450
    }
451
452
    Property(node) {
453
        this.visitProperty(node);
454
    }
455
456
    MethodDefinition(node) {
457
        this.visitProperty(node);
458
    }
459
460
    BreakStatement() {} // eslint-disable-line class-methods-use-this
461
462
    ContinueStatement() {} // eslint-disable-line class-methods-use-this
463
464
    LabeledStatement(node) {
465
        this.visit(node.body);
466
    }
467
468
    ForStatement(node) {
469
470
        // Create ForStatement declaration.
471
        // NOTE: In ES6, ForStatement dynamically generates
472
        // per iteration environment. However, escope is
473
        // a static analyzer, we only generate one scope for ForStatement.
474
        if (node.init && node.init.type === Syntax.VariableDeclaration && node.init.kind !== "var") {
475
            this.scopeManager.__nestForScope(node);
476
        }
477
478
        this.visitChildren(node);
479
480
        this.close(node);
481
    }
482
483
    ClassExpression(node) {
484
        this.visitClass(node);
485
    }
486
487
    ClassDeclaration(node) {
488
        this.visitClass(node);
489
    }
490
491
    CallExpression(node) {
492
493
        // Check this is direct call to eval
494
        if (!this.scopeManager.__ignoreEval() && node.callee.type === Syntax.Identifier && node.callee.name === "eval") {
495
496
            // NOTE: This should be `variableScope`. Since direct eval call always creates Lexical environment and
497
            // let / const should be enclosed into it. Only VariableDeclaration affects on the caller's environment.
498
            this.currentScope().variableScope.__detectEval();
499
        }
500
        this.visitChildren(node);
501
    }
502
503
    BlockStatement(node) {
504
        if (this.scopeManager.__isES6()) {
505
            this.scopeManager.__nestBlockScope(node);
506
        }
507
508
        this.visitChildren(node);
509
510
        this.close(node);
511
    }
512
513
    ThisExpression() {
514
        this.currentScope().variableScope.__detectThis();
515
    }
516
517
    WithStatement(node) {
518
        this.visit(node.object);
519
520
        // Then nest scope for WithStatement.
521
        this.scopeManager.__nestWithScope(node);
522
523
        this.visit(node.body);
524
525
        this.close(node);
526
    }
527
528
    VariableDeclaration(node) {
529
        const variableTargetScope = (node.kind === "var") ? this.currentScope().variableScope : this.currentScope();
530
531
        for (let i = 0, iz = node.declarations.length; i < iz; ++i) {
532
            const decl = node.declarations[i];
533
534
            this.visitVariableDeclaration(variableTargetScope, Variable.Variable, node, i);
535
            if (decl.init) {
536
                this.visit(decl.init);
537
            }
538
        }
539
    }
540
541
    // sec 13.11.8
542
    SwitchStatement(node) {
543
        this.visit(node.discriminant);
544
545
        if (this.scopeManager.__isES6()) {
546
            this.scopeManager.__nestSwitchScope(node);
547
        }
548
549
        for (let i = 0, iz = node.cases.length; i < iz; ++i) {
550
            this.visit(node.cases[i]);
551
        }
552
553
        this.close(node);
554
    }
555
556
    FunctionDeclaration(node) {
557
        this.visitFunction(node);
558
    }
559
560
    FunctionExpression(node) {
561
        this.visitFunction(node);
562
    }
563
564
    ForOfStatement(node) {
565
        this.visitForIn(node);
566
    }
567
568
    ForInStatement(node) {
569
        this.visitForIn(node);
570
    }
571
572
    ArrowFunctionExpression(node) {
573
        this.visitFunction(node);
574
    }
575
576
    ImportDeclaration(node) {
577
        assert(this.scopeManager.__isES6() && this.scopeManager.isModule(), "ImportDeclaration should appear when the mode is ES6 and in the module context.");
578
579
        const importer = new Importer(node, this);
580
581
        importer.visit(node);
582
    }
583
584
    visitExportDeclaration(node) {
585
        if (node.source) {
586
            return;
587
        }
588
        if (node.declaration) {
589
            this.visit(node.declaration);
590
            return;
591
        }
592
593
        this.visitChildren(node);
594
    }
595
596
    ExportDeclaration(node) {
597
        this.visitExportDeclaration(node);
598
    }
599
600
    ExportNamedDeclaration(node) {
601
        this.visitExportDeclaration(node);
602
    }
603
604
    ExportSpecifier(node) {
605
        const local = (node.id || node.local);
606
607
        this.visit(local);
608
    }
609
610
    MetaProperty() { // eslint-disable-line class-methods-use-this
611
612
        // do nothing.
613
    }
614
}
615
616
module.exports = Referencer;
617
618
/* vim: set sw=4 ts=4 et tw=80 : */
619